package com.derbysoft.lowcostadapter.dswitch2.service.impl;

import com.derbysoft.lowcostadapter.commons.dto.Book;
import com.derbysoft.lowcostadapter.commons.dto.BookResponse;
import com.derbysoft.lowcostadapter.commons.exception.HotelNotFoundException;
import com.derbysoft.lowcostadapter.commons.exception.NoAllotmentException;
import com.derbysoft.lowcostadapter.commons.model.Hotel;
import com.derbysoft.lowcostadapter.commons.model.RateCache;
import com.derbysoft.lowcostadapter.commons.model.Status;
import com.derbysoft.lowcostadapter.commons.redis.service.HotelCacheService;
import com.derbysoft.lowcostadapter.commons.redis.service.RateCacheService;
import com.derbysoft.lowcostadapter.commons.redis.utils.DateUtils;
import com.derbysoft.lowcostadapter.commons.redis.utils.KeyValueHelper;
import com.derbysoft.lowcostadapter.commons.repository.HotelRepository;
import com.derbysoft.lowcostadapter.commons.repository.ReservationRepository;
import com.derbysoft.lowcostadapter.commons.utils.DateTimeUtils;
import com.derbysoft.lowcostadapter.commons.utils.ErrorMessageUtils;
import com.derbysoft.lowcostadapter.dswitch2.service.HotelProviderService;
import com.derbysoft.lowcostadapter.dswitch2.service.helper.Constant;
import com.derbysoft.lowcostadapter.dswitch2.service.helper.ResIDHelper;
import com.derbysoft.lowcostadapter.dswitch2.translator.Translator;
import com.derbysoft.lowcostadapter.webservice.LowCostWebService;
import com.derbysoft.remote.dswitch.core.*;
import com.derbysoft.remote.dswitch.generic.constants.SmokingType;
import com.derbysoft.remote.dswitch.generic.constants.UniqueIDType;
import com.derbysoft.remote.dswitch.generic.dto.SimpleAmountDTO;
import com.derbysoft.remote.dswitch.hotel.dto.*;
import com.derbysoft.remote.share.Currency;
import com.derbysoft.remote.share.dto.TPAExtensionsDTO;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

@Service("ds2HotelReservationService")
public class HotelReservationServiceImpl
        implements HotelProviderService<GenericRQ<HotelReservationRQ>, GenericRS<HotelReservationRS>> {

    private static Logger logger = LoggerFactory.getLogger(HotelReservationServiceImpl.class);

    @Autowired
    private HotelRepository hotelRepository;

    @Autowired
    private HotelCacheService hotelCacheService;

    @Autowired
    @Qualifier("ds2HotelAvailService")
    private HotelProviderService<GenericRQ<HotelAvailRQ>, GenericRS<HotelAvailRS>> hotelAvailService;

    @Autowired
    private ReservationRepository reservationRepository;

    @Autowired
    @Qualifier("reservationWebService")
    private LowCostWebService reservationWebService;

    @Autowired
    @Qualifier("ds2HotelReservationRQTranslator")
    private Translator<GenericRQ<HotelReservationRQ>, Book, Hotel> hotelReservationRQTranslator;

    @Autowired
    @Qualifier("ds2HotelReservationRSTranslator")
    private Translator<BookResponse, GenericRS<HotelReservationRS>, GenericRQ<HotelReservationRQ>> hotelReservationRSTranslator;

    @Autowired
    private RateCacheService rateCacheService;

    @Override
    public GenericRS<HotelReservationRS> invoke(GenericRQ<HotelReservationRQ> rq) {
        try {
            createReservation(rq);

            ReservationRoomStayDTO reservationRoomStay = rq.getPayload().getHotelReservation().getRoomStays().get(0);
            Hotel hotel = loadHotel(rq.getPayload().getHotelReservation().getHotelRef().getCode());
            GenericRS<HotelAvailRS> rs = hotelAvailService.invoke(translate(rq));
            if (rs.getErrors() != null) {
                removeRates(rq, hotel);
                throw new NoAllotmentException(reservationRoomStay.getRatePlanCode(),
                        reservationRoomStay.getRoomTypeCode(), reservationRoomStay.getNumberOfUnits());
            }
            List<AvailRoomStayDTO> roomStays = rs.getPayload().getHotelAvailRoomStay().getRoomStays();
            AvailRoomStayDTO roomStayDTO = findRoomStay(roomStays, reservationRoomStay);
            if (CollectionUtils.isEmpty(roomStays) || roomStayDTO == null) {
                removeRates(rq, hotel);
                throw new NoAllotmentException(reservationRoomStay.getRatePlanCode(),
                        reservationRoomStay.getRoomTypeCode(), reservationRoomStay.getNumberOfUnits());
            }

            setTotal(reservationRoomStay, roomStayDTO);

            Book book = hotelReservationRQTranslator.translate(rq, hotel);

            BookResponse bookResponse = (BookResponse) reservationWebService.send(book);

            if (StringUtils.isNotBlank(bookResponse.getBookResult().getReturnStatus().getException())) {
                return createFailedResponse(bookResponse.getBookResult().getReturnStatus().getException(), rq);
            }

            GenericRS<HotelReservationRS> response = hotelReservationRSTranslator.translate(bookResponse, rq);
            saveReservation(response);
            return response;
        } catch (Exception ex) {
            return createFailedResponse(ErrorMessageUtils.buildErrorMessage(ex), rq);
        }
    }

    private void removeRates(GenericRQ<HotelReservationRQ> rq, Hotel hotel) {
        ReservationRoomStayDTO roomStayDTO = rq.getPayload().getHotelReservation().getRoomStays().get(0);
        String ratePlan = roomStayDTO.getRatePlanCode();
        String roomTypeId = roomStayDTO.getRoomTypeCode();
        int los = DateUtils.getDistanceBetween2Date(roomStayDTO.getStayDateRange().getCheckInDate(), roomStayDTO.getStayDateRange().getCheckOutDate());
        Date checkIn = roomStayDTO.getStayDateRange().getCheckInDate();
        String field = ratePlan + KeyValueHelper.SEPARATOR + roomTypeId;
        for (int i = 0; i < los; i++) {
            String key = RateCache.PREFIX + KeyValueHelper.SEPARATOR + hotel.getPropertyID()
                    + KeyValueHelper.SEPARATOR + DateTimeUtils.formatDate(DateUtils.addDays(checkIn, i))
                    + KeyValueHelper.SEPARATOR + roomStayDTO.getAdultCount()
                    + KeyValueHelper.SEPARATOR + roomStayDTO.getChildCount();

            RateCache beforeRemove = rateCacheService.load(key, ratePlan, roomTypeId);
            logger.info(String.format("begin remove, key:[%s], field:[%s], price:[%s]", key, field, beforeRemove == null ? "NULL" : beforeRemove.getRate().toString()));

            rateCacheService.remove(key, ratePlan, roomTypeId);

            RateCache afterRemove = rateCacheService.load(key, ratePlan, roomTypeId);
            logger.info(String.format("after remove, key:[%s], field:[%s], price:[%s]", key, field, afterRemove == null ? "NULL" : afterRemove.getRate().toString()));
        }
    }

    private GenericRS<HotelReservationRS> createFailedResponse(String errorMessage, GenericRQ<HotelReservationRQ> rq) {
        GenericRS<HotelReservationRS> genericRS = new GenericRS<HotelReservationRS>();
        genericRS.setHeader(createHeader(rq.getHeader()));
        ErrorsDTO errors = new ErrorsDTO();
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setCode("System");
        errorDTO.setSource("lowcostbeds provider side");
        errorDTO.setMessage(errorMessage);
        errors.getErrorList().add(errorDTO);
        genericRS.setErrors(errors);
        return genericRS;
    }

    private void setTotal(ReservationRoomStayDTO reservationRoomStayDTO, AvailRoomStayDTO roomStayDTO) {
        BigDecimal total = calculateTotal(roomStayDTO.getRoomRate().getRates());
        total = total.multiply(new BigDecimal(reservationRoomStayDTO.getNumberOfUnits()));
        TotalDTO totalDTO = new TotalDTO();
        totalDTO.setTotalAmount(new SimpleAmountDTO(total, Currency.GBP));
        totalDTO.setPureAmount(new SimpleAmountDTO(total, Currency.GBP));
        reservationRoomStayDTO.setTotal(totalDTO);
    }

    private ResponseHeader createHeader(RequestHeader requestHeader) {
        return new ResponseHeader(requestHeader.getTaskId());
    }

    private AvailRoomStayDTO findRoomStay(List<AvailRoomStayDTO> roomStays, ReservationRoomStayDTO reservationRoomStayDTO) {
        for (AvailRoomStayDTO roomStay : roomStays) {
            if (roomStay.getRatePlan().getCode().equalsIgnoreCase(reservationRoomStayDTO.getRatePlanCode()) &&
                    roomStay.getRoomType().getCode().equalsIgnoreCase(reservationRoomStayDTO.getRoomTypeCode())) {
                return roomStay;
            }
        }
        return null;
    }

    private BigDecimal calculateTotal(List<RateDTO> rates) {
        BigDecimal total = BigDecimal.ZERO;
        for (RateDTO rate : rates) {
            total = total.add(rate.getTotalPureAmount().getAmount());
        }
        return total;
    }

    protected Hotel loadHotel(String hotelPassport) {
        Hotel hotel = hotelCacheService.loadByPassport(hotelPassport);
        if (hotel == null) {
            hotel = hotelRepository.findByPassport(hotelPassport, Status.ENABLED);
            if (hotel == null) {
                throw new HotelNotFoundException(hotelPassport);
            }
        }
        return hotel;
    }

    private GenericRQ<HotelAvailRQ> translate(GenericRQ<HotelReservationRQ> rq) {
        GenericRQ<HotelAvailRQ> hotelAvailRQ = new GenericRQ<HotelAvailRQ>();
        hotelAvailRQ.setHeader(rq.getHeader());
        hotelAvailRQ.setPayload(createHotelAvailRQ(rq.getPayload().getHotelReservation()));
        return hotelAvailRQ;
    }

    private HotelAvailRQ createHotelAvailRQ(HotelReservationDTO hotelReservationDTO) {
        HotelAvailRQ hotelAvailRQ = new HotelAvailRQ();
        hotelAvailRQ.setAvailCriteria(createAvailCriteria(hotelReservationDTO));
        TPAExtensionsDTO tpaExtensions = new TPAExtensionsDTO();
        tpaExtensions.setElement(Constant.DISALLOW_FROM_CACHE, Boolean.TRUE.toString());
        hotelAvailRQ.setTpaExtensions(tpaExtensions);
        return hotelAvailRQ;
    }

    private HotelAvailCriteriaDTO createAvailCriteria(HotelReservationDTO hotelReservationDTO) {
        HotelAvailCriteriaDTO hotelAvailCriteriaDTO = new HotelAvailCriteriaDTO();
        hotelAvailCriteriaDTO.setHotelRef(hotelReservationDTO.getHotelRef());
        hotelAvailCriteriaDTO.setStayDateRange(hotelReservationDTO.getRoomStays().get(0).getStayDateRange());
        hotelAvailCriteriaDTO.setLanguageType(hotelReservationDTO.getLanguageType());
        hotelAvailCriteriaDTO.setRoomStayCandidates(createRoomStayCandidates(hotelReservationDTO.getRoomStays().get(0)));
        return hotelAvailCriteriaDTO;
    }

    private RoomStayCandidateDTO createRoomStayCandidates(ReservationRoomStayDTO reservationRoomStayDTO) {
        RoomStayCandidateDTO roomStayCandidateDTO = new RoomStayCandidateDTO();
        roomStayCandidateDTO.setSmoking(SmokingType.INDIFFERENT);
        roomStayCandidateDTO.setGuestCounts(reservationRoomStayDTO.getGuestCounts());
        roomStayCandidateDTO.setNumberOfUnits(reservationRoomStayDTO.getNumberOfUnits());
        return roomStayCandidateDTO;
    }

    protected void createReservation(GenericRQ<HotelReservationRQ> rq) {
        List<HotelReservationIDDTO> hotelReservationIDs = rq.getPayload().getHotelReservationIDs();
        reservationRepository.create(
                rq.getPayload().getHotelReservation().getHotelRef().getCode(),
                ResIDHelper.find(hotelReservationIDs, UniqueIDType.ERSP));
    }

    protected void saveReservation(GenericRS<HotelReservationRS> rs) {
        List<HotelReservationIDDTO> hotelReservationIDs = rs.getPayload().getHotelReservationIDs();
        reservationRepository.updateBookingReference(
                ResIDHelper.find(hotelReservationIDs, UniqueIDType.ERSP),
                ResIDHelper.find(hotelReservationIDs, UniqueIDType.HOTEL));
    }

    public void setHotelRepository(HotelRepository hotelRepository) {
        this.hotelRepository = hotelRepository;
    }

    public void setRateCacheService(RateCacheService rateCacheService) {
        this.rateCacheService = rateCacheService;
    }

    public void setHotelCacheService(HotelCacheService hotelCacheService) {
        this.hotelCacheService = hotelCacheService;
    }

    public void setHotelAvailService(HotelProviderService<GenericRQ<HotelAvailRQ>, GenericRS<HotelAvailRS>> hotelAvailService) {
        this.hotelAvailService = hotelAvailService;
    }

    public void setReservationWebService(LowCostWebService reservationWebService) {
        this.reservationWebService = reservationWebService;
    }

    public void setHotelReservationRQTranslator(Translator<GenericRQ<HotelReservationRQ>, Book, Hotel> hotelReservationRQTranslator) {
        this.hotelReservationRQTranslator = hotelReservationRQTranslator;
    }

    public void setHotelReservationRSTranslator(Translator<BookResponse, GenericRS<HotelReservationRS>, GenericRQ<HotelReservationRQ>> hotelReservationRSTranslator) {
        this.hotelReservationRSTranslator = hotelReservationRSTranslator;
    }

    public void setReservationRepository(ReservationRepository reservationRepository) {
        this.reservationRepository = reservationRepository;
    }
}